﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PrattParsing;
using sapphirelang.AST;
using sapphirelang.AST.Exprs;
using sapphirelang.AST.Stmts;

namespace sapphirelang.Parsing
{
    //[System.Diagnostics.DebuggerNonUserCode]
    class ExprParser : Parser<Expr>
    {
        // lower precedence means bound later
        const int
            // boolean operators
            PREC_OR = 10,
            PREC_AND = 15,
            PREC_NOT = 20,
            PREC_EQ = 30,
            PREC_NOTEQ = 30,
            PREC_LESS = 35,
            PREC_LESSEQ = 35,
            PREC_GREATER = 35,
            PREC_GREATEREQ = 35,

            // algebraic operators
            PREC_PLUS = 50,
            PREC_MINUS = 50,
            PREC_TIMES = 60,
            PREC_DIVIDE = 60,
            PREC_MOD = 70,
            PREC_NEG = 80,

            // other operators
            PREC_MEMBER = 100,
            PREC_CALL = 100;

        public ExprParser(IScanner scanner)
            : base(scanner)
        {
            TokenType.Init();

            ParseContext<Expr> rules = new ParseContext<Expr>();

            //// let name = expr in body
            //rules.prefix("let", lambda((parser, token) =>
            //{
            //    string name = parser.consume(TokenType.IDENT).text;
            //    parser.consume("=");
            //    Expr value = parser.parse();
            //    parser.consume("in");
            //    Expr body = parser.parse();
            //    return new BlockExpr(new Expr[] {
            //        new VarDefineExpr(name, value),
            //        body
            //    });
            //}));

            //// expr.name = expr
            //// --OR--
            //// name = expr
            //rules.infix("=", lambda(5, ASSOC_RIGHT, (Expr left, Token token, Expr right) =>
            //{
            //    if (left is VarExpr)
            //        return new VarAssignExpr(((VarExpr)left).var, right);
            //    else if (left is MemberExpr)
            //        return new MemberAssignExpr(((MemberExpr)left).obj,
            //            ((MemberExpr)left).member, right);
            //    else
            //        throw new ParseException(
            //            "Left side of an assignment must be a variable or object member.", token);
            //}));

            //// @name
            //// -- equivalent to this.name
            //rules.prefix("@", lambda((parser, token) =>
            //{
            //    string member = parser.consume(TokenType.IDENT).text;
            //    return new MemberExpr(new VarExpr("this"), member);
            //}));

            // expr op expr
            rules.infix("+", op(PREC_PLUS, ASSOC_LEFT));
            rules.infix("-", op(PREC_MINUS, ASSOC_LEFT));
            rules.infix("*", op(PREC_TIMES, ASSOC_LEFT));
            rules.infix("/", op(PREC_DIVIDE, ASSOC_LEFT));
            rules.infix("mod", op(PREC_MOD, ASSOC_LEFT));
            rules.infix("or", op(PREC_OR, ASSOC_LEFT));
            rules.infix("and", op(PREC_AND, ASSOC_LEFT));
            rules.infix("==", op(PREC_EQ, ASSOC_LEFT));
            rules.infix("!=", op(PREC_NOTEQ, ASSOC_LEFT));
            rules.infix("<", op(PREC_LESS, ASSOC_LEFT));
            rules.infix("<=", op(PREC_LESSEQ, ASSOC_LEFT));
            rules.infix(">", op(PREC_GREATER, ASSOC_LEFT));
            rules.infix(">=", op(PREC_GREATEREQ, ASSOC_LEFT));
            // op expr
            rules.prefix("-", lambda((parser, token) =>
                new CallExpr("neg", new TupleExpr(parser.parse(PREC_NEG)))));
            rules.prefix("not", lambda((parser, token) =>
                new CallExpr("not", new TupleExpr(parser.parse(PREC_NOT)))));

            // expr . name
            rules.infix(".", lambda(PREC_MEMBER, ASSOC_LEFT, infixPeriod));
            // ident
            rules.prefix(TokenType.IDENT, lambda((parser, token) =>
                new VarExpr(token.text)));
            // none
            rules.prefix("nil", lambda((parser, token) => new NilExpr()));

            // primitives
            rules.prefix(TokenType.STRING, lambda((parser, token) =>
                new StringExpr(token.getData<string>())));
            rules.prefix(TokenType.INTEGER, lambda((parser, token) =>
                new IntExpr(token.getData<long>())));
            //rules.prefix(TokenType.DOUBLE, lambda((parser, token) =>
            //    new NumExpr(token.getData<double>())));
            rules.prefix(TokenType.SYMBOL, lambda((parser, token) =>
                new SymbolExpr(token.getData<string>())));


            // [ value, value, value ... ]
            rules.prefix("[", lambda(parseArray));
            // ident ( arg )
            // -OR-
            // expr . ident ( arg )
            rules.infix("(", lambda(PREC_CALL, ASSOC_LEFT, infixParen));
            // { stmt-list }
            rules.prefix("{", lambda(stmtList));
            // ( expr )
            rules.prefix("(", lambda(prefixParen));
            // if expr then expr [else expr]
            rules.prefix("if", lambda(parseIf));

            // special EOF parselet, to signify end of parsing
            rules.prefix(Token.EOF_TYPE, lambda((parser, token) => null));

            Grammar<Expr> grammar = new Grammar<Expr>();
            grammar["base"] = rules;

            SetGrammar(grammar);
        }

        // expr . name
        Expr infixPeriod(PrattParser<Expr> parser, Expr left, Token token, int callprec)
        {
            string member = parser.consume(TokenType.IDENT).text;
            return new MessageExpr(left, member);
        }
        // [ value, value, value ... ]
        Expr parseArray(PrattParser<Expr> parser, Token token)
        {
            List<Expr> array = new List<Expr>();
            if (!parser.accept("]"))
            {
                do
                    array.Add(parser.parse());
                while (parser.accept(","));
                parser.consume("]");
            }
            return new ArrayExpr(array.ToArray());
        }
        // ident ( arg )
        // -OR-
        // expr . ident ( arg? )
        Expr infixParen(PrattParser<Expr> parser, Expr left, Token token, int callprec)
        {
            Expr arg;
            if (!parser.accept(")"))
            {
                arg = parser.parse();
                parser.consume(")");
            }
            else
                arg = Expr.None;

            // only two forms allowed are:
            // 1) expr.name ( arg )
            // 2) name ( arg )
            // a form such as expr.name(arg)(arg) is not allowed
            if (left is MessageExpr)
            {
                MessageExpr msg = (MessageExpr)left;
                if (msg.arg == null)
                {
                    // expr.ident(arg)

                    // the message expr's argument is null because
                    // when it was parsed, it did not know there was an argument.
                    // now we parsed the arg, so we assign it to the message.
                    msg.arg = arg;
                    return msg;
                }
                // if we got here, the code looked like "expr.name(arg)(arg)"
                else throw new Exception("A method call must include a name.");
            }
            else if (left is VarExpr)
            {
                // ident(arg)
                VarExpr var = (VarExpr)left;
                return new CallExpr(var.name, arg);
            }
            else throw new Exception("A method call must include a name.");
        }
        // ( expr )
        Expr prefixParen(PrattParser<Expr> parser, Token token)
        {
            Expr middle = parser.parse();
            parser.consume(")");
            return middle;
        }
        // { stmt-list }
        Expr stmtList(PrattParser<Expr> parser, Token token)
        {
            if (parser.accept("}"))
                return new BlockExpr(new Stmt[0]);
            List<Expr> body = new List<Expr>();
            while (!parser.accept("}"))
                body.Add(parser.parse());
            return new BlockExpr(body.ToArray());
        }
        // if expr then expr [else expr]
        Expr parseIf(PrattParser<Expr> parser, Token token)
        {
            Expr cond = parser.parse();
            parser.consume("then");
            Expr bodyTrue = parser.parse();
            if (parser.accept("else"))
                return new IfExpr(cond, bodyTrue, parser.parse());
            else
                return new IfExpr(cond, bodyTrue);
        }

        Expr op(Expr left, Token oper, Expr right)
        {
            return new CallExpr(oper.text, new TupleExpr(left, right));
        }
        InfixParselet<Expr> op(int prec, bool assoc)
        {
            return lambda(prec, assoc, op);
        }
    }
}
